package org.jpush.core;

import org.jpush.packages.IncomingPacket;
import org.jpush.packages.OutgoingPacket;
import org.jpush.utils.DataBuffer;
import org.jpush.utils.Pointer;

/**
 * You need to extend this <code>Protocol</code> to implement
 * the protocol of your server. Basically this class is used to
 * decode the input data into <code>IncomingPacket</code> and 
 * encode the output data to <code>OutgoingPacket</code>.
 * So you also need to implement your own <code>IncomingPacket</code>
 * and <code>OutgoingPacket</code>
 * 
 * @author Lei Wang
 *
 */
public abstract class Protocol
{
	/**
	 * The result of encoding and decoding packets
	 */
	public static enum Result
    {
        Success,
        eInsufficientBuffer,
        eIncompletePacket,
        eCorruptPacket,
        eEncodingFailure,
        eDecodingFailure,
        eUndefinedFailure,
        ePacketizingError
    }

	/**
	 * Encode the outgoing packet and write it into the buffer
	 * 
	 * @param packet  The packet to encode
	 * @param buffer  The buffer where the encoded packet is written
	 * @param nWrittenBytes  The number of bytes
	 * @return
	 */
	Result serializeOutgoingPacket(OutgoingPacket packet, DataBuffer buffer, int nWrittenBytes)
	{
		Result ret = encodeOutgoingPacket(packet);
	    if(ret == Result.Success)
	    {
		    return frameOutgoingPacket(packet, buffer, nWrittenBytes);
	    }
	    else
	    {
	        return ret;
	    }
	}

	/**
	 * Convert the incoming data from buffer into the packet 
	 * and decode this packet
	 * 
	 * @param buffer  The buffer where the data is stored
	 * @param packet  The final packet created
	 * @param serviceId  The service ID of this connection
	 * @param nExtractedBytes How many bytes of data extracted
	 * @param pContext A context about this connection
	 * @return
	 */
	Result tryDeserializeIncomingPacket(DataBuffer buffer, Pointer<IncomingPacket> packet, 
			Pointer<Integer> serviceId, Pointer<Integer> nExtractedBytes, 
			ConnectionContext pContext)
	{
		Result ret = tryDeframeIncomingPacket(buffer, packet, serviceId, nExtractedBytes, pContext);
	    if(ret == Result.Success)
	    {
		    return decodeIncomingPacket(packet.getValue(), serviceId);
	    }
	    else
	    {
	        return ret;
	    }
	}

    /**
     * Serialization
     * Override to encode the OutgoingPacket instance.
     * Save the result into the same input.
     * 
     * @param packet OutgoingPacket instance to encode.
     * @return Result @see <code>Protocol.Result</code>
     */
	public abstract Result encodeOutgoingPacket(OutgoingPacket packet);

    /**
     * Override to decode the created instance.
     * 
     * @param packet The previously deframed instance.
     * @param serviceId Service id value used to route the created instance.
     * @return Result @see <code>Protocol.Result</code>
     */
	public abstract Result decodeIncomingPacket(IncomingPacket packet, Pointer<Integer> serviceId);

    /**
     * Override to write the encoded packet into the intermediate sending buffer.
     * 
     * @param packet OutgoingPacket instance to encode.
     * @param buffer Buffer to write the result to.
     * @param nWrittenBytes Report the total bytes written.
     * @return Result @see <code>Protocol.Result</code>
     */
	protected abstract Result frameOutgoingPacket(OutgoingPacket packet, DataBuffer buffer, 
			int nWrittenBytes);

    /**
     * Deserialization
     * Override to deframe incoming packets from within the received bytes.
     * 
     * @param buffer to read data from.
     * @param packet Reference to the output pointer that should store the 
     *        address of the potentially created instance.
     * @param serviceId Service id value used to route the created instance.
     * @param nExtractedBytes Number of bytes extracted from the buffer.
     * @return Result @see <code>Protocol.Result</code>
     */
	protected abstract Result tryDeframeIncomingPacket(DataBuffer buffer, 
			Pointer<IncomingPacket> packet, Pointer<Integer> serviceId, 
			Pointer<Integer> nExtractedBytes, ConnectionContext pContext);

    /**
     * Incoming requests are created by the Protocol at time of de-serialization. 
     * When the servicing job that treats, a requests finishes, the instance is 
     * handed back to its creator for deletion. Developer can call on delete or
     * any other way of releasing the instance if a pool of requests is organized.
     * 
     * @param packet IncomingPacket instance to delete.
     * @return Result @see <code>Protocol.Result</code>
     */
    public abstract void disposeIncomingPacket(IncomingPacket packet);
}
